/*
 * Project: Set Sample
 * Copyright (C) 2013 alf.labs gmail com,
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.alflabs.setsample.stats;

import com.alflabs.annotations.NonNull;
import com.alflabs.serial.SerialReader;
import com.alflabs.serial.SerialWriter;

import java.util.Arrays;


public class SetLevel {
    private final static int AVG_WINDOW_SIZE = 10;

    private int mLevel;
    private int mGamesCompleted;
    private long mFastestTimeMs;
    private double mAvgTimeMs;
    private final long[] mLast10TimesMs = new long[AVG_WINDOW_SIZE];

    SetLevel(int nbSolutions) {
        mLevel = nbSolutions;
    }

    public int getLevel() {
        return mLevel;
    }

    public int getGamesCompleted() {
        return mGamesCompleted;
    }

    public long getFastestTimeMs() {
        return mFastestTimeMs;
    }

    public long getAvgTimeMs() {
        return (long) mAvgTimeMs;
    }

    @NonNull
    public SerialWriter toSerial() {
        SerialWriter sw = new SerialWriter();

        sw.addInt("mLevel", mLevel);
        sw.addInt("mGamesCompleted", mGamesCompleted);
        sw.addLong("mFastestTimeMs", mFastestTimeMs);
        sw.addDouble("mAvgTimeMs", mAvgTimeMs);
        sw.addInt("mLast10TimesMs_Size", AVG_WINDOW_SIZE);
        for (int i = 0; i < AVG_WINDOW_SIZE; i++) {
            sw.addLong("mLast10TimesMs_" + i, mLast10TimesMs[i]);
        }

        return sw;
    }

    public void fromSerial(@NonNull SerialReader sr) {
        mLevel = sr.getInt("mLevel");
        mGamesCompleted = sr.getInt("mGamesCompleted");
        mFastestTimeMs = sr.getLong("mFastestTimeMs");
        mAvgTimeMs = sr.getDouble("mAvgTimeMs");

        int n = sr.getInt("mLast10TimesMs_Size");
        assert n == AVG_WINDOW_SIZE;
        for (int i = 0; i < AVG_WINDOW_SIZE; i++) {
            long v = sr.getLong("mLast10TimesMs_" + i);  // returns 0 if key is missing
            mLast10TimesMs[i] = v;
        }
    }

    /**
     * Adds a new time. Returns whether this is a new fastest time.
     * Note the first game isn't marked as the fastest one (since there's no comparison.)
     */
    public boolean add(long timeMs) {
        boolean newFastest = false;

        mGamesCompleted++;

        int n = 0;
        for (; n < AVG_WINDOW_SIZE; n++) {
            if (mLast10TimesMs[n] <= 0) {
                break;
            }
        }

        if (n == 0) {
            mFastestTimeMs = timeMs;
            mAvgTimeMs = timeMs;
            mLast10TimesMs[n] = timeMs;
        } else {
            if ((timeMs/1000) < (mFastestTimeMs/1000)) {
                mFastestTimeMs = 1000 * (timeMs/1000);
                newFastest = true;
            }
            if (n < AVG_WINDOW_SIZE) {
                mAvgTimeMs = (mAvgTimeMs * n + timeMs) / (n + 1);
                mLast10TimesMs[n] = timeMs;
            } else {
                mAvgTimeMs = (mAvgTimeMs * AVG_WINDOW_SIZE + timeMs - mLast10TimesMs[0]) / AVG_WINDOW_SIZE;
                System.arraycopy(mLast10TimesMs, 1, mLast10TimesMs, 0, AVG_WINDOW_SIZE - 1);
                mLast10TimesMs[AVG_WINDOW_SIZE - 1] = timeMs;
            }
        }
        return newFastest;
    }

    @Override
    public String toString() {
        return "SetLevel{" +
                "mLevel=" + mLevel +
                ", mGames=" + mGamesCompleted +
                ", mFastestTimeMs=" + mFastestTimeMs +
                ", mAvgTimeMs=" + mAvgTimeMs +
                ", mLast10TimesMs=" + Arrays.toString(mLast10TimesMs) +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        SetLevel setLevel = (SetLevel) o;

        if (Double.compare(setLevel.mAvgTimeMs, mAvgTimeMs) != 0) return false;
        if (mFastestTimeMs != setLevel.mFastestTimeMs) return false;
        if (mLevel != setLevel.mLevel) return false;
        if (mGamesCompleted != setLevel.mGamesCompleted) return false;
        //noinspection RedundantIfStatement
        if (!Arrays.equals(mLast10TimesMs, setLevel.mLast10TimesMs)) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = mLevel;
        result = 31 * result + mGamesCompleted;
        result = 31 * result + (int) (mFastestTimeMs ^ (mFastestTimeMs >>> 32));
        temp = Double.doubleToLongBits(mAvgTimeMs);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        result = 31 * result + Arrays.hashCode(mLast10TimesMs);
        return result;
    }
}
